package com.tfswx.ruleengine.compute.util;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.tfswx.ruleengine.compute.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.AbstractFileHeader;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.lingala.zip4j.progress.ProgressMonitor;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Ricky
 * @date 2020/12/11
 */
@Slf4j
public class ZipUtil {
    public static final Charset GBK = Charset.forName("GBK");
    public static final Charset UTF_8 = StandardCharsets.UTF_8;

    private ZipUtil() {
    }

    private static final ThreadPoolExecutor POOL_EXECUTOR;

    static {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("ZIP解压进度线程池-%d").build();
        POOL_EXECUTOR = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    }

    public static void extract(File src, File dest) {
        extract(src, dest, null);
    }

    /**
     * 解压
     */
    public static void extract(File src, File dest, String password) {
        // 解压
        try {
            if (!src.exists()) {
                throw new AppException("压缩文件不存在");
            }
            Charset charset = getCharset(src);
            ZipFile zipFile = new ZipFile(src);

            // 编码格式
            zipFile.setCharset(charset);

            if (zipFile.isEncrypted()) {
                if (password == null) {
                    throw new AppException("请输入密码");
                }
                zipFile.setPassword(password.toCharArray());
            }

            CountDownLatch latch = new CountDownLatch(1);
            final ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
            AtomicReference<String> errMsg = new AtomicReference<>("");
            POOL_EXECUTOR.execute(() -> {
                long totalWork = 0;
                long time = System.currentTimeMillis();
                while (true) {
                    try {
                        if (totalWork == 0) {
                            if (System.currentTimeMillis() - time > 10000) {
                                errMsg.set("解压缩读取超时");
                                break;
                            }
                            totalWork = progressMonitor.getTotalWork();
                        } else {
                            long workCompleted = progressMonitor.getWorkCompleted();
                            if (workCompleted >= totalWork) {
                                break;
                            }
                        }
                    } finally {
                        try {
                            // 每隔50ms,发送一个解压进度出去
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                latch.countDown();
            });
            // true 在子线程中进行解压 , false主线程中解压
            zipFile.setRunInThread(true);

            zipFile.extractAll(dest.getCanonicalPath());
            latch.await();
            if (StringUtils.isNotBlank(errMsg.get())) {
                throw new AppException(errMsg.get());
            }
        } catch (IOException e) {
            throw new AppException("解压失败", e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static Charset getCharset(File src) {
        // 编码格式
        List<FileHeader> fileHeaders;
        try {
            if (!src.exists()) {
                throw new AppException("压缩文件不存在");
            }
            ZipFile zipFile = new ZipFile(src);
            fileHeaders = zipFile.getFileHeaders();
        } catch (IOException e) {
            throw new AppException("获取压缩包编码异常");
        }
        for (AbstractFileHeader fileHeader : fileHeaders) {
            boolean fileNameUtf8Encoded = fileHeader.isFileNameUTF8Encoded();
            if (!fileNameUtf8Encoded) {
                return GBK;
            }
        }
        return UTF_8;
    }

    public static void compression(String zipFilePath, List<File> fileList) {
        File zipFile = new File(zipFilePath);
        compression(zipFile, fileList);
    }

    public static void compression(File src, List<File> fileList) {
        compression(src, fileList, null, StandardCharsets.UTF_8);
    }

    /**
     * 压缩
     */
    public static void compression(File src, List<File> fileList, String password, Charset charset) {
        // 压缩
        try {
            File parentFile = src.getParentFile();
            if (parentFile != null && !parentFile.exists()) {
                FileUtils.forceMkdirParent(src);
            }
            if (src.exists()) {
                src.delete();
            }
            //设置参数
            ZipParameters para = new ZipParameters();
            //设置压缩方式,默认是COMP_DEFLATE
            para.setCompressionMethod(CompressionMethod.DEFLATE);
            //设置压缩级别,默认为0（即不压缩）
            para.setCompressionLevel(CompressionLevel.NORMAL);
            ZipFile zipFile = new ZipFile(src);
            if (StringUtils.isNotBlank(password)) {
                // 是否密码
                para.setEncryptFiles(true);
                // 加密方式
                para.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
                zipFile.setPassword(password.toCharArray());
            }
            zipFile.setCharset(charset);
            zipFile.addFiles(fileList, para);
        } catch (IOException e) {
            throw new AppException("压缩失败", e);
        }
    }
}
